/*
tmock: Taller Technologies mock
    Copyright (C) 2014  Daniel Gutson, Andres Agustin Tiraboschi, Taller Technologies

    This file is part of tmock.

    tmock is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    tmock is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with tmock.  If not, see <http://www.gnu.org/licenses/>.
*/

#include "parser.h"

static string preamble;

static inline void consumeSpaces(string& str)
{
    size_t pos = str.find_first_not_of(" ");
    str.erase(0, pos);
}

static string intToString(const int i)
{
    ostringstream s;
    s << i;
    string i_as_string(s.str());
    return i_as_string;
}

static inline int stringToInt(const string str)
{
    return atoi(str.c_str());
}

static string getToken(string& line)
{
    size_t pos;
    string tok;
    consumeSpaces(line);
    pos = line.find(" ", 0);
    tok = line.substr(0, pos);
    line.erase(0, pos);
    return tok;
}

string FunctionToMock::parseType(string& line)
{
    string type = getToken(line);
    if (!type.empty())
    {
        if (type[0] == '@')
            type = "const " + type.substr(1);
        else if (type[0] == '#')
            type = "volatile " + type.substr(1);
    }
    return type;
}

/*convert the list of arguments to string*/
string FunctionToMock::argsToString(const bool type, const bool variable)
{
    string args;
    int argNum = 0;
    args += "(";
    list<string>::iterator it;
    it = listArgs.begin();
    while (it != listArgs.end())
    {
        if (type)
        {
            args += *it;
            if ((*it).compare("void") == 0)
                return args + ")";
        }
        if (variable)
        {
            if ((*it).compare("void") != 0)
                args += " x" + intToString(argNum);
        }
        ++it;
        if (it == listArgs.end())
            args += ")";
        else
            args += ",";
        ++argNum;
    }
    return args;
}

string FunctionToMock::getName(void) const
{
    return name;
}

const int FunctionToMock::getId(void) const
{
    return id;
}

void FunctionToMock::insertFunctionToTest(const string name)
{
    functionsToTest.push_back(name);
}

const bool FunctionToMock::isFunctionToTest(const string name, const string end)
{
    list<string>::iterator it;
    for (it = functionsToTest.begin(); it != functionsToTest.end(); ++it)
    {
        string ftt = *it;
        if (name.compare(ftt + end) == 0)
            return true;
    }
    return false;
}

/*parse a line from list.tmock*/
void FunctionToMock::parse(string line)
{
    size_t pos;
    id = stringToInt(getToken(line));
    returnType = parseType(line);
    if (returnType.empty())
        throw "No return type";
    name = getToken(line);
    if (name.empty())
        throw "Function without name";
    string arg = parseType(line);
    if (arg.empty())
        throw "Function with no arguments";
    listArgs.push_back(arg);
    arg = parseType(line);
    while (!arg.empty())
    {
        listArgs.push_back(arg);
        arg = parseType(line);
    }

}

string FunctionToMock::declaration(const bool external, const bool original)
{
    string decl;
    if (external)
        decl += "extern ";
    decl += returnType + " " + name;
    if (original)
        decl += "_original";
    decl += argsToString(true, true) + ";";
    return decl;
}

string FunctionToMock::pointer(const bool external, const bool constant, const bool initialized, const string endl, const string endr)
{
    string decl;
    if (external)
        decl += "extern ";
    decl += returnType;
    decl += "(*";
    if (constant)
        decl += "const";
    decl += " " + name + endl;
    decl += ")";
    decl += argsToString(true, true);
    if (initialized)
        decl += "=" + name + endr;
    decl += ";";
    return decl;
}

string FunctionToMock::definition(const string prev, const bool used)
{
    string def;
    def += prev + returnType + " ";
    if (used)
        def += "__attribute__ ((used)) ";
    def += name;
    def += "_wrapper";
    def += argsToString(true, true) + string("{return ");
    def += name + "_pointer" + argsToString(false, true) + ";}";
    return def;
}

static FunctionToMock* getFunctionWithId(const int id, Functions &functions)
{
    Functions::iterator it;
    for (it = functions.begin(); it != functions.end(); ++it)
    {
        FunctionToMock func = *it;
        if (func.getId() == id)
        {
            return &(*it);
        }
    }
    return NULL;
}

Functions parseMockListFile(const string inputFile, size_t &line)
{
    ifstream listFile(inputFile.c_str());
    if (!listFile)
        throw "Mocklist file not found";
    Functions functions;
    string functionToMockLine;
    while (getline(listFile, functionToMockLine))
    {
        ++line;
        consumeSpaces(functionToMockLine);
        if (!functionToMockLine.empty())
        {
            /* distinguish between preamble lines and function lines*/
            if (functionToMockLine[0] == ';')
                preamble += functionToMockLine.substr(1) + "\n";
            else
            {
                const int lineType = stringToInt(getToken(functionToMockLine));
                if (lineType == 1)
                {
                    FunctionToMock ftm;
                    ftm.declared = false;
                    ftm.fntree = NULL_TREE;
                    ftm.parse(functionToMockLine);
                    if (getFunction(ftm.getName(), "", functions) != NULL)
                        throw "Function " + ftm.getName() + " is mocked twice";
                    functions.push_back(ftm);
                }
                else if (lineType == 2)
                {
                    const int id =  stringToInt(getToken(functionToMockLine));
                    FunctionToMock* ftm = getFunctionWithId(id, functions);
                    string name = getToken(functionToMockLine);
                    if (ftm == NULL)
                        throw "No mock function found for " + name;
                    else
                        ftm->insertFunctionToTest(name);
                }
                else
                    throw "Invalid option";
            }
        }
    }
    listFile.close();
    return functions;
}

void writeIncMainFile(const string outputFile, Functions &functions)
{
    ofstream incFile(outputFile.c_str());
    if (!incFile)
        throw "Error creating incFile";
    incFile.write(preamble.c_str(), preamble.size());
    Functions::iterator it;
    for (it = functions.begin(); it != functions.end(); ++it)
    {
        FunctionToMock func = *it;
        string decl = func.declaration(false, false);
        string pointer = func.pointer(false, false, true, "_pointer", "");
        incFile.write((decl + "\n").c_str(), decl.size() + 1);
        incFile.write((pointer + "\n").c_str(), pointer.size() + 1);
    }
    incFile.close();
}

/*get the function that it's name: functionName + end = name*/
FunctionToMock* getFunction(const string name, const string end, Functions &functions)
{
    Functions::iterator it;
    for (it = functions.begin(); it != functions.end(); ++it)
    {
        FunctionToMock func = *it;
        if ((func.getName() + end).compare(name) == 0)
        {
            return &(*it);
        }
    }
    return NULL;
}
